/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#ifndef __LYNX_MATERIAL_HPP_INCLUDED__
#define __LYNX_MATERIAL_HPP_INCLUDED__

#include "LynxTexture.hpp"

namespace Lynx
{

//! Material flags
enum E_MATERIAL_FLAG
{
    //! Draw as wireframe or filled triangles? Default: false
    EMF_WIREFRAME = 0x1,

    //! Draw as point cloud or filled triangles? Default: false
    EMF_POINTCLOUD = 0x2,

    //! Flat or Gouraud shading? Default: true
    EMF_GOURAUD_SHADING = 0x4,

    //! Will this material be lighted? Default: true
    EMF_LIGHTING = 0x8,

    //! Is the ZBuffer enabled? Default: true
    EMF_ZBUFFER = 0x10,

    //! May be written to the zbuffer or is it readonly. Default: true
    /** This flag is ignored, if the material type is a transparent type. */
    EMF_ZWRITE_ENABLE = 0x20,

    //! Is backface culling enabled? Default: true
    EMF_BACK_FACE_CULLING = 0x40,

    //! Is frontface culling enabled? Default: false
    /** Overrides EMF_BACK_FACE_CULLING if both are enabled. */
    EMF_FRONT_FACE_CULLING = 0x80,

    //! Is bilinear filtering enabled? Default: true
    EMF_BILINEAR_FILTER = 0x100,

    //! Is trilinear filtering enabled? Default: false
    /** If the trilinear filter flag is enabled,
    the bilinear filtering flag is ignored. */
    EMF_TRILINEAR_FILTER = 0x200,

    //! Is anisotropic filtering? Default: false
    /** In Irrlicht you can use anisotropic texture filtering in
    conjunction with bilinear or trilinear texture filtering
    to improve rendering results. Primitives will look less
    blurry with this flag switched on. */
    EMF_ANISOTROPIC_FILTER = 0x400,

    //! Is fog enabled? Default: false
    EMF_FOG_ENABLE = 0x800,

    //! Normalizes normals. Default: false
    /** You can enable this if you need to scale a dynamic lighted
    model. Usually, its normals will get scaled too then and it
    will get darker. If you enable the EMF_NORMALIZE_NORMALS flag,
    the normals will be normalized again, and the model will look
    as bright as it should. */
    EMF_NORMALIZE_NORMALS = 0x1000,

    //! Access to all layers texture wrap settings. Overwrites separate layer settings.
    EMF_TEXTURE_WRAP = 0x2000,

    //! AntiAliasing mode
    EMF_ANTI_ALIASING = 0x4000,

    //! ColorMask bits, for enabling the color planes
    EMF_COLOR_MASK = 0x8000,

    //! ColorMaterial enum for vertex color interpretation
    EMF_COLOR_MATERIAL = 0x10000
};

//! Flag for EMT_ONETEXTURE_BLEND, ( BlendFactor ) BlendFunc = source * sourceFactor + dest * destFactor
enum E_BLEND_FACTOR
{
    EBF_ZERO	= 0,		//!< src & dest	(0, 0, 0, 0)
    EBF_ONE,			//!< src & dest	(1, 1, 1, 1)
    EBF_DST_COLOR, 			//!< src	(destR, destG, destB, destA)
    EBF_ONE_MINUS_DST_COLOR, 	//!< src	(1-destR, 1-destG, 1-destB, 1-destA)
    EBF_SRC_COLOR,			//!< dest	(srcR, srcG, srcB, srcA)
    EBF_ONE_MINUS_SRC_COLOR, 	//!< dest	(1-srcR, 1-srcG, 1-srcB, 1-srcA)
    EBF_SRC_ALPHA,			//!< src & dest	(srcA, srcA, srcA, srcA)
    EBF_ONE_MINUS_SRC_ALPHA,	//!< src & dest	(1-srcA, 1-srcA, 1-srcA, 1-srcA)
    EBF_DST_ALPHA,			//!< src & dest	(destA, destA, destA, destA)
    EBF_ONE_MINUS_DST_ALPHA,	//!< src & dest	(1-destA, 1-destA, 1-destA, 1-destA)
    EBF_SRC_ALPHA_SATURATE		//!< src	(min(srcA, 1-destA), idem, ...)
};

enum E_COLOR_PLANE
{
    //! No color enabled
    ECP_NONE=0,
    //! Alpha enabled
    ECP_ALPHA=1,
    //! Red enabled
    ECP_RED=2,
    //! Green enabled
    ECP_GREEN=4,
    //! Blue enabled
    ECP_BLUE=8,
    //! All colors, no alpha
    ECP_RGB=14,
    //! All planes enabled
    ECP_ALL=15
};

//! Abstracted and easy to use fixed function/programmable pipeline material modes.
enum E_MATERIAL_TYPE
{
    //! Standard solid material.
    /** Only first texture is used, which is supposed to be the
    diffuse material. */
    EMT_SOLID = 0,

    //! Solid material with 2 texture layers.
    /** The second is blended onto the first using the alpha value
    of the vertex colors. This material is currently not implemented in OpenGL.
    */
    EMT_SOLID_2_LAYER,

    //! Material type with standard lightmap technique
    /** There should be 2 textures: The first texture layer is a
    diffuse map, the second is a light map. Dynamic light is
    ignored. */
    EMT_LIGHTMAP,

    //! Material type with lightmap technique like EMT_LIGHTMAP.
    /** But lightmap and diffuse texture are added instead of modulated. */
    EMT_LIGHTMAP_ADD,

    //! Material type with standard lightmap technique
    /** There should be 2 textures: The first texture layer is a
    diffuse map, the second is a light map. Dynamic light is
    ignored. The texture colors are effectively multiplied by 2
    for brightening. Like known in DirectX as D3DTOP_MODULATE2X. */
    EMT_LIGHTMAP_M2,

    //! Material type with standard lightmap technique
    /** There should be 2 textures: The first texture layer is a
    diffuse map, the second is a light map. Dynamic light is
    ignored. The texture colors are effectively multiplyied by 4
    for brightening. Like known in DirectX as D3DTOP_MODULATE4X. */
    EMT_LIGHTMAP_M4,

    //! Like EMT_LIGHTMAP, but also supports dynamic lighting.
    EMT_LIGHTMAP_LIGHTING,

    //! Like EMT_LIGHTMAP_M2, but also supports dynamic lighting.
    EMT_LIGHTMAP_LIGHTING_M2,

    //! Like EMT_LIGHTMAP_4, but also supports dynamic lighting.
    EMT_LIGHTMAP_LIGHTING_M4,

    //! Detail mapped material.
    /** The first texture is diffuse color map, the second is added
    to this and usually displayed with a bigger scale value so that
    it adds more detail. The detail map is added to the diffuse map
    using ADD_SIGNED, so that it is possible to add and substract
    color from the diffuse map. For example a value of
    (127,127,127) will not change the appearance of the diffuse map
    at all. Often used for terrain rendering. */
    EMT_DETAIL_MAP,

    //! Look like a reflection of the environment around it.
    /** To make this possible, a texture called 'sphere map' is
    used, which must be set as the first texture. */
    EMT_SPHERE_MAP,

    //! A reflecting material with an optional non reflecting texture layer.
    /** The reflection map should be set as first texture. */
    EMT_REFLECTION_2_LAYER,

    //! A transparent material.
    /** Only the first texture is used. The new color is calculated
    by simply adding the source color and the dest color. This
    means if for example a billboard using a texture with black
    background and a red circle on it is drawn with this material,
    the result is that only the red circle will be drawn a little
    bit transparent, and everything which was black is 100%
    transparent and not visible. This material type is useful for
    particle effects. */
    EMT_TRANSPARENT_ADD_COLOR,

    //! Makes the material transparent based on the texture alpha channel.
    /** The final color is blended together from the destination
    color and the texture color, using the alpha channel value as
    blend factor. Only first texture is used. If you are using
    this material with small textures, it is a good idea to load
    the texture in 32 bit mode
    (video::IVideoDriver::set_texture_creation_flag()). Also, an alpha
    ref is used, which can be manipulated using
    SMaterial::MaterialTypeParam. This value controls how sharp the
    edges become when going from a transparent to a solid spot on
    the texture. */
    EMT_TRANSPARENT_ALPHA_CHANNEL,

    //! Makes the material transparent based on the texture alpha channel.
    /** If the alpha channel value is greater than 127, a
    pixel is written to the target, otherwise not. This
    material does not use alpha blending and is a lot faster
    than EMT_TRANSPARENT_ALPHA_CHANNEL. It is ideal for drawing
    stuff like leafes of plants, because the borders are not
    blurry but sharp. Only first texture is used. If you are
    using this material with small textures and 3d object, it
    is a good idea to load the texture in 32 bit mode
    (video::IVideoDriver::set_texture_creation_flag()). */
    EMT_TRANSPARENT_ALPHA_CHANNEL_REF,

    //! Makes the material transparent based on the vertex alpha value.
    EMT_TRANSPARENT_VERTEX_ALPHA,

    //! A transparent reflecting material with an optional additional non reflecting texture layer.
    /** The reflection map should be set as first texture. The
    transparency depends on the alpha value in the vertex colors. A
    texture which will not reflect can be set as second texture.
    Please note that this material type is currently not 100%
    implemented in OpenGL. */
    EMT_TRANSPARENT_REFLECTION_2_LAYER,

    //! A solid normal map renderer.
    /** First texture is the color map, the second should be the
    normal map. Note that you should use this material only when
    drawing geometry consisting of vertices of type
    S3DVertexTangents (EVT_TANGENTS). You can convert any mesh into
    this format using IMeshManipulator::create_mesh_with_tangents()
    (See SpecialFX2 Tutorial). This shader runs on vertex shader
    1.1 and pixel shader 1.1 capable hardware and falls back to a
    fixed function lighted material if this hardware is not
    available. Only two lights are supported by this shader, if
    there are more, the nearest two are chosen. */
    EMT_NORMAL_MAP_SOLID,

    //! A transparent normal map renderer.
    /** First texture is the color map, the second should be the
    normal map. Note that you should use this material only when
    drawing geometry consisting of vertices of type
    S3DVertexTangents (EVT_TANGENTS). You can convert any mesh into
    this format using IMeshManipulator::create_mesh_with_tangents()
    (See SpecialFX2 Tutorial). This shader runs on vertex shader
    1.1 and pixel shader 1.1 capable hardware and falls back to a
    fixed function lighted material if this hardware is not
    available. Only two lights are supported by this shader, if
    there are more, the nearest two are chosen. */
    EMT_NORMAL_MAP_TRANSPARENT_ADD_COLOR,

    //! A transparent (based on the vertex alpha value) normal map renderer.
    /** First texture is the color map, the second should be the
    normal map. Note that you should use this material only when
    drawing geometry consisting of vertices of type
    S3DVertexTangents (EVT_TANGENTS). You can convert any mesh into
    this format using IMeshManipulator::create_mesh_with_tangents()
    (See SpecialFX2 Tutorial). This shader runs on vertex shader
    1.1 and pixel shader 1.1 capable hardware and falls back to a
    fixed function lighted material if this hardware is not
    available.  Only two lights are supported by this shader, if
    there are more, the nearest two are chosen. */
    EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA,

    //! Just like EMT_NORMAL_MAP_SOLID, but uses parallax mapping.
    /** Looks a lot more realistic. This only works when the
    hardware supports at least vertex shader 1.1 and pixel shader
    1.4. First texture is the color map, the second should be the
    normal map. The normal map texture should contain the height
    value in the alpha component. The
    IVideoDriver::make_normal_map_texture() method writes this value
    automatically when creating normal maps from a heightmap when
    using a 32 bit texture. The height scale of the material
    (affecting the bumpiness) is being controlled by the
    SMaterial::MaterialTypeParam member. If set to zero, the
    default value (0.02f) will be applied. Otherwise the value set
    in SMaterial::MaterialTypeParam is taken. This value depends on
    with which scale the texture is mapped on the material. Too
    high or low values of MaterialTypeParam can result in strange
    artifacts. */
    EMT_PARALLAX_MAP_SOLID,

    //! A material like EMT_PARALLAX_MAP_SOLID, but transparent.
    /** Using EMT_TRANSPARENT_ADD_COLOR as base material. */
    EMT_PARALLAX_MAP_TRANSPARENT_ADD_COLOR,

    //! A material like EMT_PARALLAX_MAP_SOLID, but transparent.
    /** Using EMT_TRANSPARENT_VERTEX_ALPHA as base material. */
    EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA,

    //! BlendFunc = source * sourceFactor + dest * destFactor ( E_BLEND_FUNC )
    /** Using only first texture. Generic blending method. */
    EMT_ONETEXTURE_BLEND,

    //! This value is not used. It only forces this enumeration to compile to 32 bit.
    EMT_FORCE_32BIT = 0x7fffffff
};

//! Array holding the built in material type names
const char* const sBuiltInMaterialTypeNames[] =
{
    "solid",
    "solid_2layer",
    "lightmap",
    "lightmap_add",
    "lightmap_m2",
    "lightmap_m4",
    "lightmap_light",
    "lightmap_light_m2",
    "lightmap_light_m4",
    "detail_map",
    "sphere_map",
    "reflection_2layer",
    "trans_add",
    "trans_alphach",
    "trans_alphach_ref",
    "trans_vertex_alpha",
    "trans_reflection_2layer",
    "normalmap_solid",
    "normalmap_trans_add",
    "normalmap_trans_vertexalpha",
    "parallaxmap_solid",
    "parallaxmap_trans_add",
    "parallaxmap_trans_vertexalpha",
    "onetexture_blend",
    0
};

//! Comparison function, e.g. for depth buffer test
enum E_COMPARISON_FUNC
{
    //! Test never succeeds, this equals disable
    ECF_NEVER=0,
    //! <= test, default for e.g. depth test
    ECF_LESSEQUAL=1,
    //! Exact equality
    ECF_EQUAL=2,
    //! exclusive less comparison, i.e. <
    ECF_LESS,
    //! Succeeds almost always, except for exact equality
    ECF_NOTEQUAL,
    //! >= test
    ECF_GREATEREQUAL,
    //! inverse of <=
    ECF_GREATER,
    //! test succeeds always
    ECF_ALWAYS
};

//! These flags are used to specify the anti-aliasing and smoothing modes
/** Techniques supported are multisampling, geometry smoothing, and alpha
to coverage.
Some drivers don't support a per-material setting of the anti-aliasing
modes. In those cases, FSAA/multisampling is defined by the device mode
chosen upon creation via irr::SIrrCreationParameters.
*/
enum E_ANTI_ALIASING_MODE
{
    //! Use to turn off anti-aliasing for this material
    EAAM_OFF=0,
    //! Default anti-aliasing mode
    EAAM_SIMPLE=1,
    //! High-quality anti-aliasing, not always supported, automatically enables SIMPLE mode
    EAAM_QUALITY=3,
    //! Line smoothing
    EAAM_LINE_SMOOTH=4,
    //! point smoothing, often in software and slow, only with OpenGL
    EAAM_POINT_SMOOTH=8,
    //! All typical anti-alias and smooth modes
    EAAM_FULL_BASIC=15,
    //! Enhanced anti-aliasing for transparent materials
    /** Usually used with EMT_TRANSPARENT_ALPHA_REF and multisampling. */
    EAAM_ALPHA_TO_COVERAGE=16
};

//! These flags allow to define the interpretation of vertex color when lighting is enabled
/** Without lighting being enabled the vertex color is the only value defining the fragment color.
Once lighting is enabled, the four values for diffuse, ambient, emissive, and specular take over.
With these flags it is possible to define which lighting factor shall be defined by the vertex color
instead of the lighting factor which is the same for all faces of that material.
The default is to use vertex color for the diffuse value, another pretty common value is to use
vertex color for both diffuse and ambient factor. */
enum E_COLOR_MATERIAL
{
    //! Don't use vertex color for lighting
    ECM_NONE=0,
    //! Use vertex color for diffuse light, this is default
    ECM_DIFFUSE,
    //! Use vertex color for ambient light
    ECM_AMBIENT,
    //! Use vertex color for emissive light
    ECM_EMISSIVE,
    //! Use vertex color for specular light
    ECM_SPECULAR,
    //! Use vertex color for both diffuse and ambient light
    ECM_DIFFUSE_AND_AMBIENT
};

//! MaterialTypeParam: e.g. DirectX: D3DTOP_MODULATE, D3DTOP_MODULATE2X, D3DTOP_MODULATE4X
enum E_MODULATE_FUNC
{
    EMF_MODULATE_1X	= 1,
    EMF_MODULATE_2X	= 2,
    EMF_MODULATE_4X	= 4
};


//! Source of the alpha value to take
/** This is currently only supported in EMT_ONETEXTURE_BLEND. You can use an
or'ed combination of values. Alpha values are modulated (multiplicated). */
enum E_ALPHA_SOURCE
{
    //! Use no alpha, somewhat redundant with other settings
    EAS_NONE=0,
    //! Use vertex color alpha
    EAS_VERTEX_COLOR,
    //! Use texture alpha channel
    EAS_TEXTURE
};

//! Texture coord clamp mode outside [0.0, 1.0]
enum E_TEXTURE_CLAMP
{
    //! Texture repeats
    ETC_REPEAT = 0,
    //! Texture is clamped to the last pixel
    ETC_CLAMP,
    //! Texture is clamped to the edge pixel
    ETC_CLAMP_TO_EDGE,
    //! Texture is clamped to the border pixel (if exists)
    ETC_CLAMP_TO_BORDER,
    //! Texture is alternatingly mirrored (0..1..0..1..0..)
    ETC_MIRROR,
    //! Texture is mirrored once and then clamped (0..1..0)
    ETC_MIRROR_CLAMP,
    //! Texture is mirrored once and then clamped to edge
    ETC_MIRROR_CLAMP_TO_EDGE,
    //! Texture is mirrored once and then clamped to border
    ETC_MIRROR_CLAMP_TO_BORDER
};

//! Struct for holding material parameters which exist per texture layer
class TextureLayer
{
public:
    //! Default constructor
    TextureLayer() :
        mTexture(0),
        mTextureWrapU(ETC_REPEAT),
        mTextureWrapV(ETC_REPEAT),
        mBilinearFilter(true),
        mTrilinearFilter(false),
        mAnisotropicFilter(0),
        mLODBias(0),
        mTextureMatrix(0)
    {
        //
    }

    //! Copy constructor
    /** \param other Material layer to copy from. */
    TextureLayer(const TextureLayer& other)
    {
        // This pointer is checked during assignment
        mTextureMatrix = 0;
        *this = other;
    }

    //! Destructor
    ~TextureLayer()
    {
        mMatrixAllocator.destruct(mTextureMatrix);
        mMatrixAllocator.deallocate(mTextureMatrix);
    }

    //! Assignment operator
    /** \param other Material layer to copy from.
    \return This material layer, updated. */
    TextureLayer& operator=(const TextureLayer& other)
    {
        // Check for self-assignment!
        if(this == &other)
            return *this;

        mTexture = other.mTexture;
        if(mTextureMatrix)
        {
            if(other.mTextureMatrix)
                *mTextureMatrix = *other.mTextureMatrix;
            else
            {
                mMatrixAllocator.destruct(mTextureMatrix);
                mMatrixAllocator.deallocate(mTextureMatrix);
                mTextureMatrix = 0;
            }
        }
        else
        {
            if(other.mTextureMatrix)
            {
                mTextureMatrix = mMatrixAllocator.allocate(1);
                mMatrixAllocator.construct(mTextureMatrix,*other.mTextureMatrix);
            }
            else
                mTextureMatrix = 0;
        }
        mTextureWrapU = other.mTextureWrapU;
        mTextureWrapV = other.mTextureWrapV;
        mBilinearFilter = other.mBilinearFilter;
        mTrilinearFilter = other.mTrilinearFilter;
        mAnisotropicFilter = other.mAnisotropicFilter;
        mLODBias = other.mLODBias;

        return *this;
    }

    //! Gets the texture transformation matrix
    /** \return Texture matrix of this layer. */
    Myth::Math::Matrix4 get_texture_matrix()
    {
        if(!mTextureMatrix)
        {
            mTextureMatrix = mMatrixAllocator.allocate(1);
            mMatrixAllocator.construct(mTextureMatrix, Myth::Math::Matrix4());
        }
        return *mTextureMatrix;
    }

    //! Gets the immutable texture transformation matrix
    /** \return Texture matrix of this layer. */
    const Myth::Math::Matrix4 get_texture_matrix() const
    {
        if(mTextureMatrix)
            return *mTextureMatrix;
        else
            return Myth::Math::Matrix4();
    }

    //! Sets the texture transformation matrix to mat
    /** \param mat New texture matrix for this layer. */
    void set_texture_matrix(const Myth::Math::Matrix4 mat)
    {
        if(!mTextureMatrix)
        {
            mTextureMatrix = mMatrixAllocator.allocate(1);
            mMatrixAllocator.construct(mTextureMatrix, mat);
        }
        else
            *mTextureMatrix = mat;
    }

    //! Inequality operator
    /** \param b Layer to compare to.
    \return True if layers are different, else false. */
    inline bool operator!=(const TextureLayer& b) const
    {
        bool different =
            mTexture != b.mTexture ||
            mTextureWrapU != b.mTextureWrapU ||
            mTextureWrapV != b.mTextureWrapV ||
            mBilinearFilter != b.mBilinearFilter ||
            mTrilinearFilter != b.mTrilinearFilter ||
            mAnisotropicFilter != b.mAnisotropicFilter ||
            mLODBias != b.mLODBias;
        if(different)
            return true;
        else
            different |= (mTextureMatrix != b.mTextureMatrix) &&
                         mTextureMatrix && b.mTextureMatrix &&
                         (*mTextureMatrix != *(b.mTextureMatrix));
        return different;
    }

    //! Equality operator
    /** \param b Layer to compare to.
    \return True if layers are equal, else false. */
    inline bool operator==(const TextureLayer& b) const
    {
        return !(b!=*this);
    }

    //! Texture
    Texture* mTexture;

    //! Texture Clamp Mode
    /** Values are tkane from E_TEXTURE_CLAMP. */
    uint8_t mTextureWrapU;
    uint8_t mTextureWrapV;

    //! Is bilinear filtering enabled? Default: true
    bool mBilinearFilter;

    //! Is trilinear filtering enabled? Default: false
    /** If the trilinear filter flag is enabled,
    the bilinear filtering flag is ignored. */
    bool mTrilinearFilter;

    //! Is anisotropic filtering enabled? Default: 0, disabled
    /** In Irrlicht you can use anisotropic texture filtering
    in conjunction with bilinear or trilinear texture
    filtering to improve rendering results. Primitives
    will look less blurry with this flag switched on. The number gives
    the maximal anisotropy degree, and is often in the range 2-16.
    Value 1 is equivalent to 0, but should be avoided. */
    uint8_t mAnisotropicFilter;

    //! Bias for the mipmap choosing decision.
    /** This value can make the textures more or less blurry than with the
    default value of 0. The value (divided by 8.f) is added to the mipmap level
    chosen initially, and thus takes a smaller mipmap for a region
    if the value is positive. */
    int8_t mLODBias;

private:
    friend class Material;
    Myth::Memory::Allocator<Myth::Math::Matrix4> mMatrixAllocator;

    //! Texture Matrix
    /** Do not access this element directly as the internal
    ressource management has to cope with Null pointers etc. */
    Myth::Math::Matrix4* mTextureMatrix;
};

//! Struct for holding parameters for a material renderer
class Material
{
public:
    //! Default constructor. Creates a solid, lit material with white colors
    Material() :
        mMaterialType(EMT_SOLID),
        mAmbientColor(255,255,255,255),
        mDiffuseColor(255,255,255,255),
        mEmissiveColor(0,0,0,0),
        mSpecularColor(255,255,255,255),
        mShininess(0.0f),
        mMaterialTypeParam(0.0f),
        mMaterialTypeParam2(0.0f),
        mThickness(1.0f),
        mZBuffer(ECF_LESSEQUAL),
        mAntiAliasing(EAAM_SIMPLE),
        mColorMask(ECP_ALL),
        mColorMaterial(ECM_DIFFUSE),
        mWireframe(false),
        mPointCloud(false),
        mGouraudShading(true),
        mLighting(true),
        mZWriteEnable(true),
        mBackfaceCulling(true),
        mFrontfaceCulling(false),
        mFogEnable(false),
        mNormalizeNormals(false)
    {
        //
    }

    //! Copy constructor
    /** \param other Material to copy from. */
    Material(const Material& other)
    {
        for(uint32_t i = 0; i < 8; ++i)
            mTextureLayer[i].mTextureMatrix = 0;
        *this = other;
    }

    //! Assignment operator
    /** \param other Material to copy from. */
    Material& operator=(const Material& other)
    {
        if(this == &other)
            return *this;

        mMaterialType = other.mMaterialType;

        mAmbientColor = other.mAmbientColor;
        mDiffuseColor = other.mDiffuseColor;
        mEmissiveColor = other.mEmissiveColor;
        mSpecularColor = other.mSpecularColor;
        mShininess = other.mShininess;
        mMaterialTypeParam = other.mMaterialTypeParam;
        mMaterialTypeParam2 = other.mMaterialTypeParam2;
        mThickness = other.mThickness;

        for(uint32_t i = 0; i < 8; ++i)
        {
            mTextureLayer[i] = other.mTextureLayer[i];
        }

        mWireframe = other.mWireframe;
        mPointCloud = other.mPointCloud;
        mGouraudShading = other.mGouraudShading;
        mLighting = other.mLighting;
        mZWriteEnable = other.mZWriteEnable;
        mBackfaceCulling = other.mBackfaceCulling;
        mFrontfaceCulling = other.mFrontfaceCulling;
        mFogEnable = other.mFogEnable;
        mNormalizeNormals = other.mNormalizeNormals;
        mZBuffer = other.mZBuffer;
        mAntiAliasing = other.mAntiAliasing;
        mColorMask = other.mColorMask;
        mColorMaterial = other.mColorMaterial;

        return *this;
    }

    //! Texture layer array.
    TextureLayer mTextureLayer[8];

    //! Type of the material. Specifies how everything is blended together
    E_MATERIAL_TYPE mMaterialType;

    //! How much ambient light (a global light) is reflected by this material.
    /** The default is full white, meaning objects are completely
    globally illuminated. Reduce this if you want to see diffuse
    or specular light effects. */
    Color mAmbientColor;

    //! How much diffuse light coming from a light source is reflected by this material.
    /** The default is full white. */
    Color mDiffuseColor;

    //! Light emitted by this material. Default is to emit no light.
    Color mEmissiveColor;

    //! How much specular light (highlights from a light) is reflected.
    /** The default is to reflect white specular light. See
    SMaterial::Shininess on how to enable specular lights. */
    Color mSpecularColor;

    //! Value affecting the size of specular highlights.
    /** A value of 20 is common. If set to 0, no specular
    highlights are being used. To activate, simply set the
    shininess of a material to a value in the range [0.5;128]:
    \code
    sceneNode->get_material(0).Shininess = 20.0f;
    \endcode

    You can change the color of the highlights using
    \code
    sceneNode->get_material(0).SpecularColor.set(255,255,255,255);
    \endcode

    The specular color of the dynamic lights
    (SLight::SpecularColor) will influence the the highlight color
    too, but they are set to a useful value by default when
    creating the light scene node. Here is a simple example on how
    to use specular highlights:
    \code
    // load and display mesh
    scene::IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode(
    smgr->getMesh("data/faerie.md2"));
    node->setMaterialTexture(0, driver->get_texture("data/Faerie2.pcx")); // set diffuse texture
    node->set_material_flag(video::EMF_LIGHTING, true); // enable dynamic lighting
    node->get_material(0).Shininess = 20.0f; // set size of specular highlights

    // add white light
    scene::ILightSceneNode* light = smgr->addLightSceneNode(0,
    	core::vector3df(5,5,5), video::SColorf(1.0f, 1.0f, 1.0f));
    \endcode */
    float mShininess;

    //! Free parameter, dependent on the material type.
    /** Mostly ignored, used for example in EMT_PARALLAX_MAP_SOLID
    and EMT_TRANSPARENT_ALPHA_CHANNEL. */
    float mMaterialTypeParam;

    //! Second free parameter, dependent on the material type.
    /** Mostly ignored. */
    float mMaterialTypeParam2;

    //! Thickness of non-3dimensional elements such as lines and points.
    float mThickness;

    //! Is the ZBuffer enabled? Default: ECFN_LESSEQUAL
    /** Values are from E_COMPARISON_FUNC. */
    uint8_t mZBuffer;

    //! Sets the antialiasing mode
    /** Values are chosen from E_ANTI_ALIASING_MODE. Default is
    EAAM_SIMPLE|EAAM_LINE_SMOOTH, i.e. simple multi-sample
    anti-aliasing and lime smoothing is enabled. */
    uint8_t mAntiAliasing;

    //! Defines the enabled color planes
    /** Values are defined as or'ed values of the E_COLOR_PLANE enum.
    Only enabled color planes will be rendered to the current render
    target. Typical use is to disable all colors when rendering only to
    depth or stencil buffer, or using Red and Green for Stereo rendering. */
    uint8_t mColorMask;

    //! Defines the interpretation of vertex color in the lighting equation
    /** Values should be chosen from E_COLOR_MATERIAL.
    When lighting is enabled, vertex color can be used instead of the
    material values for light modulation. This allows to easily change e.g. the
    diffuse light behavior of each face. The default, ECM_DIFFUSE, will result in
    a very similar rendering as with lighting turned off, just with light shading. */
    uint8_t mColorMaterial;

    //! Draw as wireframe or filled triangles? Default: false
    /** The user can access a material flag using
    \code material.Wireframe=true \endcode
    or \code material.set_flag(EMF_WIREFRAME, true); \endcode */
    bool mWireframe;

    //! Draw as point cloud or filled triangles? Default: false
    bool mPointCloud;

    //! Flat or Gouraud shading? Default: true
    bool mGouraudShading;

    //! Will this material be lighted? Default: true
    bool mLighting;

    //! Is the zbuffer writeable or is it read-only. Default: true.
    /** This flag is forced to false if the MaterialType is a
    transparent type and the scene parameter
    ALLOW_ZWRITE_ON_TRANSPARENT is not set. */
    bool mZWriteEnable;

    //! Is backface culling enabled? Default: true
    bool mBackfaceCulling;

    //! Is frontface culling enabled? Default: false
    bool mFrontfaceCulling;

    //! Is fog enabled? Default: false
    bool mFogEnable;

    //! Should normals be normalized?
    /** Always use this if the mesh lit and scaled. Default: false */
    bool mNormalizeNormals;

    //! Gets the texture transformation matrix for level i
    /** \param i The desired level. Must not be larger than MATERIAL_MAX_TEXTURES.
    \return Texture matrix for texture level i. */
    Myth::Math::Matrix4 get_texture_matrix(uint32_t i)
    {
        return mTextureLayer[i].get_texture_matrix();
    }

    //! Gets the immutable texture transformation matrix for level i
    /** \param i The desired level.
    \return Texture matrix for texture level i, or identity matrix for levels larger than MATERIAL_MAX_TEXTURES. */
    const Myth::Math::Matrix4 get_texture_matrix(uint32_t i) const
    {
        if(i < 8)
            return mTextureLayer[i].get_texture_matrix();
        else
            return Myth::Math::Matrix4();
    }

    //! Sets the i-th texture transformation matrix
    /** \param i The desired level.
    \param mat Texture matrix for texture level i. */
    void set_texture_matrix(uint32_t i, const Myth::Math::Matrix4 mat)
    {
        if(i >= 8)
            return;
        mTextureLayer[i].set_texture_matrix(mat);
    }

    //! Gets the i-th texture
    /** \param i The desired level.
    \return Texture for texture level i, if defined, else 0. */
    Texture* get_texture(uint32_t i) const
    {
        return i < 8 ? mTextureLayer[i].mTexture : 0;
    }

    //! Sets the i-th texture
    /** If i>=MATERIAL_MAX_TEXTURES this setting will be ignored.
    \param i The desired level.
    \param tex Texture for texture level i. */
    void set_texture(uint32_t i, Texture* tex)
    {
        if(i >= 8)
            return;
        mTextureLayer[i].mTexture = tex;
    }

    //! Sets the Material flag to the given value
    /** \param flag The flag to be set.
    \param value The new value for the flag. */
    void set_flag(E_MATERIAL_FLAG flag, bool value)
    {
        switch(flag)
        {
        case EMF_WIREFRAME:
            mWireframe = value;
            break;
        case EMF_POINTCLOUD:
            mPointCloud = value;
            break;
        case EMF_GOURAUD_SHADING:
            mGouraudShading = value;
            break;
        case EMF_LIGHTING:
            mLighting = value;
            break;
        case EMF_ZBUFFER:
            mZBuffer = value;
            break;
        case EMF_ZWRITE_ENABLE:
            mZWriteEnable = value;
            break;
        case EMF_BACK_FACE_CULLING:
            mBackfaceCulling = value;
            break;
        case EMF_FRONT_FACE_CULLING:
            mFrontfaceCulling = value;
            break;
        case EMF_BILINEAR_FILTER:
        {
            for(uint32_t i = 0; i < 8; ++i)
                mTextureLayer[i].mBilinearFilter = value;
        }
        break;
        case EMF_TRILINEAR_FILTER:
        {
            for(uint32_t i = 0; i < 8; ++i)
                mTextureLayer[i].mTrilinearFilter = value;
        }
        break;
        case EMF_ANISOTROPIC_FILTER:
        {
            if(value)
                for(uint32_t i = 0; i < 8; ++i)
                    mTextureLayer[i].mAnisotropicFilter = 0xFF;
            else
                for(uint32_t i = 0; i < 8; ++i)
                    mTextureLayer[i].mAnisotropicFilter = 0;
        }
        break;
        case EMF_FOG_ENABLE:
            mFogEnable = value;
            break;
        case EMF_NORMALIZE_NORMALS:
            mNormalizeNormals = value;
            break;
        case EMF_TEXTURE_WRAP:
        {
            for(uint32_t i = 0; i < 8; ++i)
            {
                mTextureLayer[i].mTextureWrapU = (E_TEXTURE_CLAMP)value;
                mTextureLayer[i].mTextureWrapV = (E_TEXTURE_CLAMP)value;
            }
        }
        break;
        case EMF_ANTI_ALIASING:
            mAntiAliasing = value ? EAAM_SIMPLE : EAAM_OFF;
            break;
        case EMF_COLOR_MASK:
            mColorMask = value ? ECP_ALL : ECP_NONE;
            break;
        case EMF_COLOR_MATERIAL:
            mColorMaterial = value ? ECM_DIFFUSE : ECM_NONE;
            break;
        default:
            break;
        }
    }

    //! Gets the Material flag
    /** \param flag The flag to query.
    \return The current value of the flag. */
    bool get_flag(E_MATERIAL_FLAG flag) const
    {
        switch(flag)
        {
        case EMF_WIREFRAME:
            return mWireframe;
        case EMF_POINTCLOUD:
            return mPointCloud;
        case EMF_GOURAUD_SHADING:
            return mGouraudShading;
        case EMF_LIGHTING:
            return mLighting;
        case EMF_ZBUFFER:
            return mZBuffer!=ECF_NEVER;
        case EMF_ZWRITE_ENABLE:
            return mZWriteEnable;
        case EMF_BACK_FACE_CULLING:
            return mBackfaceCulling;
        case EMF_FRONT_FACE_CULLING:
            return mFrontfaceCulling;
        case EMF_BILINEAR_FILTER:
            return mTextureLayer[0].mBilinearFilter;
        case EMF_TRILINEAR_FILTER:
            return mTextureLayer[0].mTrilinearFilter;
        case EMF_ANISOTROPIC_FILTER:
            return mTextureLayer[0].mAnisotropicFilter != 0;
        case EMF_FOG_ENABLE:
            return mFogEnable;
        case EMF_NORMALIZE_NORMALS:
            return mNormalizeNormals;
        case EMF_TEXTURE_WRAP:
            return !(mTextureLayer[0].mTextureWrapU ||
                     mTextureLayer[0].mTextureWrapV ||
                     mTextureLayer[1].mTextureWrapU ||
                     mTextureLayer[1].mTextureWrapV ||
                     mTextureLayer[2].mTextureWrapU ||
                     mTextureLayer[2].mTextureWrapV ||
                     mTextureLayer[3].mTextureWrapU ||
                     mTextureLayer[3].mTextureWrapV);
        case EMF_ANTI_ALIASING:
            return (mAntiAliasing==1);
        case EMF_COLOR_MASK:
            return (mColorMask!=ECP_NONE);
        case EMF_COLOR_MATERIAL:
            return (mColorMaterial != ECM_NONE);
        }

        return false;
    }

    //! Inequality operator
    /** \param b Material to compare to.
    \return True if the materials differ, else false. */
    inline bool operator!=(const Material& b) const
    {
        bool different =
            mMaterialType != b.mMaterialType ||
            mAmbientColor != b.mAmbientColor ||
            mDiffuseColor != b.mDiffuseColor ||
            mEmissiveColor != b.mEmissiveColor ||
            mSpecularColor != b.mSpecularColor ||
            mShininess != b.mShininess ||
            mMaterialTypeParam != b.mMaterialTypeParam ||
            mMaterialTypeParam2 != b.mMaterialTypeParam2 ||
            mThickness != b.mThickness ||
            mWireframe != b.mWireframe ||
            mPointCloud != b.mPointCloud ||
            mGouraudShading != b.mGouraudShading ||
            mLighting != b.mLighting ||
            mZBuffer != b.mZBuffer ||
            mZWriteEnable != b.mZWriteEnable ||
            mBackfaceCulling != b.mBackfaceCulling ||
            mFrontfaceCulling != b.mFrontfaceCulling ||
            mFogEnable != b.mFogEnable ||
            mNormalizeNormals != b.mNormalizeNormals ||
            mAntiAliasing != b.mAntiAliasing ||
            mColorMask != b.mColorMask ||
            mColorMaterial != b.mColorMaterial;
        for(uint32_t i = 0; (i < 8) && !different; ++i)
        {
            different |= (mTextureLayer[i] != b.mTextureLayer[i]);
        }
        return different;
    }

    //! Equality operator
    /** \param b Material to compare to.
    \return True if the materials are equal, else false. */
    inline bool operator==(const Material& b) const
    {
        return !(b!=*this);
    }

    bool is_transparent() const
    {
        return mMaterialType == EMT_TRANSPARENT_ADD_COLOR ||
               mMaterialType == EMT_TRANSPARENT_ALPHA_CHANNEL ||
               mMaterialType == EMT_TRANSPARENT_VERTEX_ALPHA ||
               mMaterialType == EMT_TRANSPARENT_REFLECTION_2_LAYER;
    }
};

class OverrideMaterial
{
public:
    //! The Material values
    Material mMaterial;
    //! Which values are taken for override
    /** OR'ed values from E_MATERIAL_FLAGS. */
    uint32_t mEnableFlags;
    //! Set in which render passes the material override is active.
    /** OR'ed values from E_SCENE_NODE_RENDER_PASS. */
    uint16_t mEnablePasses;
    //! Global enable flag, overwritten by the SceneManager in each pass
    /** The Scenemanager uses the EnablePass array and sets Enabled to
    true if the Override material is enabled in the current pass. */
    bool mEnabled;

    //! Default constructor
    OverrideMaterial() :
        mEnableFlags(0),
        mEnablePasses(0),
        mEnabled(false)
    {
        //
    }

    //! Apply the enabled overrides
    void apply(Material& material)
    {
        if(mEnabled)
        {
            for(uint32_t i = 0; i < 32; ++i)
            {
                const uint32_t num = (1<<i);
                if(mEnableFlags & num)
                {
                    switch(num)
                    {
                    case EMF_WIREFRAME:
                        material.mWireframe = mMaterial.mWireframe;
                        break;
                    case EMF_POINTCLOUD:
                        material.mPointCloud = mMaterial.mPointCloud;
                        break;
                    case EMF_GOURAUD_SHADING:
                        material.mGouraudShading = mMaterial.mGouraudShading;
                        break;
                    case EMF_LIGHTING:
                        material.mLighting = mMaterial.mLighting;
                        break;
                    case EMF_ZBUFFER:
                        material.mZBuffer = mMaterial.mZBuffer;
                        break;
                    case EMF_ZWRITE_ENABLE:
                        material.mZWriteEnable = mMaterial.mZWriteEnable;
                        break;
                    case EMF_BACK_FACE_CULLING:
                        material.mBackfaceCulling = mMaterial.mBackfaceCulling;
                        break;
                    case EMF_FRONT_FACE_CULLING:
                        material.mFrontfaceCulling = mMaterial.mFrontfaceCulling;
                        break;
                    case EMF_FOG_ENABLE:
                        material.mFogEnable = mMaterial.mFogEnable;
                        break;
                    case EMF_NORMALIZE_NORMALS:
                        material.mNormalizeNormals = mMaterial.mNormalizeNormals;
                        break;
                    case EMF_ANTI_ALIASING:
                        material.mAntiAliasing = mMaterial.mAntiAliasing;
                        break;
                    case EMF_COLOR_MASK:
                        material.mColorMask = mMaterial.mColorMask;
                        break;
                    case EMF_BILINEAR_FILTER:
                        material.mTextureLayer[0].mBilinearFilter = mMaterial.mTextureLayer[0].mBilinearFilter;
                        break;
                    case EMF_TRILINEAR_FILTER:
                        material.mTextureLayer[0].mTrilinearFilter = mMaterial.mTextureLayer[0].mTrilinearFilter;
                        break;
                    case EMF_ANISOTROPIC_FILTER:
                        material.mTextureLayer[0].mAnisotropicFilter = mMaterial.mTextureLayer[0].mAnisotropicFilter;
                        break;
                    case EMF_TEXTURE_WRAP:
                        material.mTextureLayer[0].mTextureWrapU = mMaterial.mTextureLayer[0].mTextureWrapU;
                        material.mTextureLayer[0].mTextureWrapV = mMaterial.mTextureLayer[0].mTextureWrapV;
                        break;
                    }
                }
            }
        }
    }

};

//! EMT_ONETEXTURE_BLEND: pack srcFact, dstFact, Modulate and alpha source to MaterialTypeParam
/** alpha source can be an OR'ed combination of E_ALPHA_SOURCE values. */
inline float pack_texture_blend_func(const E_BLEND_FACTOR srcFact, const E_BLEND_FACTOR dstFact, const E_MODULATE_FUNC modulate = EMF_MODULATE_1X, const uint32_t alphaSource = EAS_TEXTURE)
{
    const uint32_t tmp = (alphaSource << 12) | (modulate << 8) | (srcFact << 4) | dstFact;
    return ((float&)(tmp));
}

//! EMT_ONETEXTURE_BLEND: unpack srcFact & dstFact and Modulo to MaterialTypeParam
/** The fields don't use the full byte range, so we could pack even more... */
inline void unpack_texture_blend_func(E_BLEND_FACTOR& srcFact, E_BLEND_FACTOR& dstFact, E_MODULATE_FUNC& modulo, uint32_t& alphaSource, const float param)
{
    const uint32_t state = ((uint32_t&)(param));
    alphaSource = (state & 0x0000F000) >> 12;
    modulo	= E_MODULATE_FUNC((state & 0x00000F00) >> 8);
    srcFact = E_BLEND_FACTOR((state & 0x000000F0) >> 4);
    dstFact = E_BLEND_FACTOR((state & 0x0000000F));
}

//! EMT_ONETEXTURE_BLEND: has BlendFactor Alphablending
inline bool texture_blend_func_has_alpha(const E_BLEND_FACTOR factor)
{
    switch(factor)
    {
    case EBF_SRC_ALPHA:
    case EBF_ONE_MINUS_SRC_ALPHA:
    case EBF_DST_ALPHA:
    case EBF_ONE_MINUS_DST_ALPHA:
    case EBF_SRC_ALPHA_SATURATE:
        return true;
    default:
        return false;
    }
}

} // namespace Lynx

#endif // __LYNX_MATERIAL_HPP_INCLUDED__

